# coding:utf-8
from db import *
from mongoengine import *
from bson import ObjectId
import random
import functools


def update(worldTime, factory):
  # revive mobs died and reach revive_time
  for mob in Mob.objects(dead_time__ne=-1):
    if mob.revive_time + mob.dead_time <= worldTime:
      print "revive mob"
      # Revive it base on prototype
      mobType = MobType.objects(__raw__={'_id': ObjectId(mob.tid)}).first()
      # Revive it to a random position
      area = Area.objects(__raw__={'_id': ObjectId(mob.area_id)}).first()
      sub_area_ids = map(lambda x: x._id , Area.objects(__raw__={'pid': ObjectId(area.pid)}))
      area_id = random.choice(sub_area_ids)
      Mob.objects(__raw__={'_id': ObjectId(mob._id)}).update_one(dead_time=-1,hp=mobType.hp,area_id=area_id)
      # Broadcast to all players in this position
      forceRefreshMobs(factory, str(area_id))
  # Revive players died and reach revie_time
  for p in Player.objects(dead_time__ne=-1):
    if worldTime >= p.dead_time + 2: # Revive after 2 minute
      Player.objects(__raw__={'_id': ObjectId(p._id)}).update_one(dead_time=-1,hp=p.max_hp)
      forceRefreshPlayers(factory, str(p.area_id))
      
def drop_from_mob(mobTypeId):
  drops = Drop.objects(__raw__={'mobtype_id': ObjectId(mobTypeId)})
  rateCount = reduce(lambda acc,cur: acc + cur.rate, drops, 0)
  randNum = random.randint(1,rateCount)
  cursor = 0
  drop = None
  for d in drops: 
    cursor += d.rate
    if cursor >= randNum:
      drop = d
      break
  return drop
  # return drop.dict() if drop is not None else {}

def addInventoryAndForceRefresh(inventory, client):
  if inventory.num < 0:
    return

  exist = Inventory.objects(__raw__={'player_id': inventory.player_id,'type': inventory.type, 'thing_id': inventory.thing_id}).first()

  if exist is None:
    print "Exist is None"
    Inventory.objects().insert(inventory)
  else:
    exist.num += inventory.num
    exist.save()

  # force refresh inventory
  forceRefreshInventory(client)



def attack_mob(mob,player,now, client):
  [p,m] = battle(player,mob,0)
  result = {}
  if p.hp <= 0:
    print "Player died"
    p.dead_time = now
    p.save()
    result['win'] = False
  else:
    print "Player win"
    m.dead_time = now
    # Exp up
    p = levelUp(player,m)
    p.save()# Save the status after battle
    m.save()
    result['win'] = True
    # Drop things and add to inventory and force player refresh inventory
    drop = drop_from_mob(mob.tid)
    if drop.num > 0:
      addInventoryAndForceRefresh(Inventory(player_id=player._id, thing_id=drop.thing_id, type=drop.type, num=drop.num), client)
      result['drop'] = drop.dict()
    else:
      result['drop'] = None
    print ' '.join(['Drop: ',str(drop.dict())])
  return result

  # return True if p.hp < 0 else False
  
def levelUp(player,enemy):
  exp = player.exp + ((enemy.power * 2) if hasattr(enemy,'username') else enemy.power * 10)
  if exp > (player.level + 5) * 5 + 50 : # level up
    exp = 0
    player.level += 1
    player.max_hp += player.growth_factor
    player.power += player.growth_factor
  player.exp = exp
  return player


def battle(src,tar,turn):
  # print ' '.join(['turn:', str(turn), 'src_hp:', str(src.hp), 'tar_hp:', str(tar.hp)])
  if src.hp <= 0 or tar.hp <= 0:
    return [src,tar]
  else:
    src.hp -= tar.power
    tar.hp -= src.power
    turn+=1
    return battle(src,tar,turn)

def attack_player(src,tar,factory):
  [src,tar] = battle(src,tar,0)
  now = factory.worldTime
  result_msg = ''
  if src.hp == 0 & tar.hp == 0: # Ended in a draw
    result_msg = ' '.join([src.name,'try to attack player',tar.name,'but both of them died!'])
  elif src.hp == 0: # Tar win
    result_msg = ' '.join([src.name,'try to attack player',tar.name,'but was killed by target!'])
    src.dead_time = now
    tar = levelUp(tar,src)
  else: # Src win
    result_msg = ' '.join([src.name,'kill the player',tar.name,'!'])
    tar.dead_time = now
    src = levelUp(src,tar)
  src.save()
  tar.save()
  broadcastAllPlayers(factory,result_msg)
  forceRefreshPlayers(factory,str(src.area_id))
  


def broadcastAllPlayers(factory,msg):
  for c in factory.clients:
    c.sendMessage(json.dumps({'url': 'announce','msg': ' '.join(['World:',msg])}))

def isClientInPlayers(players,client):
  remain = filter(lambda p: p.username == client.user.name, players)
  return False if not remain else True

def clientsInPosition(area_id,factory):
  playersToWarn = Player.objects(__raw__={'area_id': ObjectId(area_id)})
  return filter(functools.partial(isClientInPlayers, playersToWarn), factory.clients)

def broadcastToClientsInPosition(factory,area_id,data_producer):
  clients = clientsInPosition(area_id,factory)
  if not clients:
    print "No clients in it"
  else:
    data = data_producer()
    for c in clients:
      c.sendMessage(json.dumps(data))

def forceRefreshPlayers(factory,area_id):
  def producer():
    players = {}
    players['players'] = map(lambda x: x.dict(), Player.objects(__raw__={'area_id': ObjectId(area_id), 'online': {'$ne':False}}))
    players['url'] = 'players'
    return players
  broadcastToClientsInPosition(factory,area_id,producer)

def forceRefreshMobs(factory,area_id):
  def producer():
    data = {}
    data['mobs'] = map(lambda x: x.dict(), Mob.objects(__raw__={'area_id': ObjectId(area_id)}))
    data['url'] = 'mobs'
    return data
  broadcastToClientsInPosition(factory,area_id,producer)
  

def forcePositionPlayersRefreshMap(factory,area_id):
  def producer():
    data = {}
    data['mobs'] = map(lambda x: x.dict(), Mob.objects(__raw__={'area_id': ObjectId(area_id)}))
    data['players'] = map(lambda x: x.dict(), Player.objects(__raw__={'area_id': ObjectId(area_id), 'online': {'$ne':False}}))
    data['url'] = 'lives'
    return data
  broadcastToClientsInPosition(factory,area_id,producer)


def lives(area_id):
  data = {}
  data['mobs'] = map(lambda x: x.dict(), Mob.objects(__raw__={'area_id': ObjectId(area_id)}))
  data['players'] = map(lambda x: x.dict(), Player.objects(__raw__={'area_id': ObjectId(area_id), 'online': {'$ne':False}}))
  data['url'] = 'lives'
  return data


def positionAnounce(factory,area_id,msg):
  clients = clientsInPosition(area_id,factory)
  for c in clients:
    c.sendMessage(json.dumps({'url': 'announce', 'msg': ' '.join(['Position: ',msg])}))

def forceRefreshInventory(client):
  inventory = Inventory.objects(__raw__={'player_id': client.player._id})
  res = {'url': 'inventory', 'inventory': map(lambda x: x.dict(), inventory)}
  client.sendMessage(json.dumps(res))

def forceRefreshEquiping(client):
  res = {'ok': True}
  res['url'] = 'equiping'
  res['equipings'] = map(lambda x: x.dict() , Equiping.objects(player_id=client.player._id))
  client.sendMessage(json.dumps(res))

def anounceToPlayer(client,msg,type='log'):
  # Type: "warning"、"error"、 "success"、"info" and "question"
  client.sendMessage(json.dumps({'url': 'announce', 'msg': msg, 'type': type}))

def forceRefreshPlayer(client, player):
  client.sendMessage(json.dumps({'url': 'myself', 'player': player.dict()}))

def useItem(client, inventory_id):
  inventory = Inventory.objects(__raw__={'_id': ObjectId(inventory_id)}).first()
  item = Item.objects(__raw__={'_id': ObjectId(inventory.thing_id)}).first()
  if item.name == 'apple':
    # Recover HP, force refresh himself, return successful message
    player = Player.objects(__raw__={'_id': client.player._id}).first()
    player.hp += 30
    player.hp = player.hp if player.hp < player.max_hp else player.max_hp
    player.save()
    forceRefreshPlayer(client, player)
    client.player = player
    return {'ok': True}
  return {'ok': False, 'err': 'Unknown item'}


def spawnMob(typename,num=1):
  mt = MobType.objects(name=typename).first()
  # Revive it to a random position
  area = Area.objects(name='beginer village').first()
  sub_area_ids = map(lambda x: x._id , Area.objects(__raw__={'pid': area._id}))
  for time in range(num):
    area_id = random.choice(sub_area_ids)
    Mob.objects().insert(Mob(name=mt.name,power=mt.power,hp=mt.hp,growth_factor=mt.growth_factor,revive_time=mt.revive_time,max_level=mt.max_level,life_span=mt.life_span,upgrade_time=mt.upgrade_time, desc=mt.desc, living_time=0, tid=mt._id, area_id=area_id, level=1, dead_time=-1))

def inventoryNumDecrease(inventoryId):
  # Decrease item num
  inventory = Inventory.objects(__raw__={'_id': ObjectId(inventoryId)}).first()
  if inventory.num > 1:
    inventory.num -= 1
    inventory.save()
  else:
    Inventory.objects(__raw__={'_id': ObjectId(inventoryId)}).delete()


# @return: equiping document
def samePartEquiping(part,playerId):
  for equiping in Equiping.objects(player_id=playerId):
    equip = Equip.objects(_id=equiping.equip_id).first()
    if equip.part == part:
      return equiping
  return None

# Take off the equip then add it to the player's inventory and decrease player's attributes
def takeOffPartEquip(part,client):
  player = client.player
  for equiping in Equiping.objects(player_id=player._id):
    equip = Equip.objects(_id=equiping.equip_id).first()
    if equip.part == part:
      # delete from equiping
      Equiping.objects(_id=equiping._id).delete()
      addInventoryAndForceRefresh(Inventory(player_id=player._id, thing_id=equip._id, type='equip', num=1), client)
      # decrease attribute
      player.hp -= equip.hp
      player.power -= equip.power
      player.save()

def takeOffEquip(equiping_id ,  client):
  player = client.player
  player_id = player._id
  equiping = Equiping.objects(_id=equiping_id).first()
  equip = Equip.objects(_id=equiping.equip_id).first()
  Equiping.objects(_id=equiping_id).delete()
  # decrease attribute
  player.hp -= equip.hp
  player.power -= equip.power
  player.save()

  addInventoryAndForceRefresh(Inventory(player_id=player_id, thing_id=equip._id, type='equip', num=1), client)
  forceRefreshEquiping(client)
  forceRefreshPlayer(client,player)

def equiupings(player_id):
  return map(lambda e: Equip.objects(_id=e.equip_id).first(), Equiping.objects(player_id=player_id))


  

def wearEquip(equipId, client):
  player = client.player
  inventory = Inventory.objects(__raw__={'_id': ObjectId(equipId)}).first()
  equip = Equip.objects(__raw__={'_id': ObjectId(inventory.thing_id)}).first()

  takeOffPartEquip(equip.part, client)
  
  Equiping.objects.insert(Equiping(equip_id=equip._id, player_id=player._id))
  # Decrease from inventory
  inventory = Inventory.objects(player_id=player._id, thing_id=equip._id).first()
  inventoryNumDecrease(inventory._id)
  # Increase player attributes
  player.hp += equip.hp
  player.power += equip.power
  player.save()

  # Refresh inventory and player himself
  forceRefreshPlayer(client,player)
  forceRefreshInventory(client)
  forceRefreshEquiping(client)